# _*_ encoding: utf-8 _*_
import os
from simple_controller import load_vehicles
from simple_controller import load_main_road
import logging
import optparse
import sys
sys.path.append("..")
import traci
import traci.constants as tc


try:
    import xml.etree.cElementTree as ET
except ImportError:
    import xml.etree.ElementTree as ET


class SimpleVelocityController:
    def __init__(self, rou_file):
        self.vehicles, self.departTable = load_vehicles(rou_file)
        pass

    def check(self):
        assert self.vehicles is not None
        assert self.departTable is not None
        print "Controller Pass the Check!"

    def release(self):
        del self.vehicles
        del self.departTable
        print "Controller released!"

    def process(self, current_step, edges, rt_edge, alter_table):
        for depart in range(1, 300):
            mark_table = []
            next_step = depart + current_step
            if next_step not in self.departTable:
                continue
            for vehicle_id in self.departTable[next_step]:
                for r_id in self.vehicles[vehicle_id]['route']:
                    if edges[r_id]['length'] < 20:
                        continue
                    capacity = int(edges[r_id]['length'] / 7.5)
                    occupy = rt_edge[r_id][1] * 1.0 / capacity
                    if float(rt_edge[r_id][0]) < float(edges[r_id]['speed']) * 0.2 and occupy >= 0.7:
                        new_depart = int(self.vehicles[vehicle_id]['depart']) + 600
                        if vehicle_id not in alter_table:
                            alter_table[vehicle_id] = new_depart
                            self.vehicles[vehicle_id]['depart'] = new_depart
                            self.departTable.setdefault(new_depart, {})
                            self.departTable[new_depart].setdefault(vehicle_id, 1)
                            mark_table.append(vehicle_id)
                        break
            for v_id in mark_table:
                self.departTable[next_step].pop(v_id)
            del mark_table
        return alter_table


class AverageVelocityController(SimpleVelocityController):

    def process(self, current_step, edges, rt_edge, alter_table):
        for depart in range(1, 300):
            mark_table = []
            next_step = depart + current_step
            if next_step not in self.departTable:
                continue
            for vehicle_id in self.departTable[next_step]:
                total_distance = 0
                total_time = 0
                for r_id in self.vehicles[vehicle_id]['route']:
                    if edges[r_id]['length'] < 20:
                        continue
                    capacity = int(edges[r_id]['length'] / 7.5)
                    total_distance += float(edges[r_id]['length'])
                    if capacity * 0.3 >= rt_edge[r_id][1]:
                        total_time += float(edges[r_id]['length']) / rt_edge[r_id][0]
                    else:
                        total_time += float(edges[r_id]['length']) / (edges[r_id]['speed'] * 0.8)
                avg = total_distance / total_time
                if avg < 5.6:  # average speed is below 20KM/h
                    new_depart = int(self.vehicles[vehicle_id]['depart']) + 300
                    if vehicle_id not in alter_table:
                        alter_table[vehicle_id] = new_depart
                        self.vehicles[vehicle_id]['depart'] = new_depart
                        self.departTable.setdefault(new_depart, {})
                        self.departTable[new_depart].setdefault(vehicle_id, 1)
                        mark_table.append(vehicle_id)
            for v_id in mark_table:
                self.departTable[next_step].pop(v_id)
            del mark_table
        return alter_table


class Connector:
    def __init__(self, network_file, port, host, start, end):
        self.edges = load_main_road(network_file)
        self.port = port
        self.host = host
        self.controller = None
        self.start = start
        self.end = end
        self.rt_edges = {}

    def retrieve_edges(self, current_time):
        del self.rt_edges
        self.rt_edges = {}
        for edge_id in self.edges:
            subscribe = traci.edge.getSubscriptionResults(edge_id)
            mean_speed = self.edges[edge_id]['length'] / subscribe[tc.VAR_CURRENT_TRAVELTIME]
            subscribe = [mean_speed, subscribe[tc.LAST_STEP_VEHICLE_NUMBER],\
                         subscribe[tc.VAR_FUELCONSUMPTION]]
            self.rt_edges.setdefault(edge_id, subscribe)
            logging.info("type 0 time {0}, edge {1}, mean speed {2}, vehicle number {3}, fuel consumption {4}".\
                         format(current_time, edge_id, subscribe[0], subscribe[1], subscribe[2]))

    def subscribe(self):
        for edge_id in self.edges:
            traci.edge.subscribe(edgeID=edge_id, varIDs=(tc.VAR_CURRENT_TRAVELTIME, tc.LAST_STEP_VEHICLE_NUMBER, \
                                                         tc.VAR_FUELCONSUMPTION))

    def check(self):
        assert self.edges is not None
        assert self.controller is not None
        assert self.host is not None
        assert self.port is not None
        assert self.start < self.end
        self.controller.check()
        print "Connector pass the check!"

    def simulate(self):
        self.check()
        print "Start simulate!"
        traci.init(port=self.port, host=self.host)
        self.subscribe()
        for step in range(self.start, self.end):
            print "step {0}".format(step)
            traci.simulationStep()
            current_time = int(traci.simulation.getCurrentTime() / 1000)
            self.retrieve_edges(current_time)
            self.reassign_depart(current_time)
        self.release()

    def release(self):
        traci.close()
        del self.edges
        self.controller.release()
        print "connector released!"

    def register_controller(self, controller):
        assert isinstance(controller, SimpleVelocityController)
        if self.controller is None:
            self.controller = controller
        else:
            print("Controller has already been registered")
            exit(1)

    def reassign_depart(self, step):
        alter_table = {}
        self.controller.process(step, self.edges, self.rt_edges, alter_table)
        for vehicle_Id in alter_table:
            traci.vehicle.setDepart(vehicle_Id, alter_table[vehicle_Id])
            logging.info("type 1 time {0}, vehicle {1} change depart to {2}".format(step, vehicle_Id, alter_table[vehicle_Id]))
        del alter_table


def get_options(args=None):
    optparser = optparse.OptionParser()
    optparser.add_option("-l", "--logs", dest="logs", help="The output log file")
    optparser.add_option("-r", "--rou-file", dest="rou", help="The route file")
    optparser.add_option("-n", "--net-file", dest="net", help="The road network file")
    optparser.add_option("-p", "--port", dest="port", type="int", help="The remote server port")
    optparser.add_option("-s", "--server", dest="server", help="The server ip address")
    optparser.add_option("-b", "--begin", dest="start", type="int", help="The simulate begin time")
    optparser.add_option("-e", "--end", dest="end", type="int", help="The simulate end time")
    optparser.add_option("-c", "--sumo-cfg", dest="cfg", help="The sumo scenario configuration file")
    optparser.add_option("--class", dest="class_name", help="The controller")

    (options, args) = optparser.parse_args(args=args)
    if options.cfg is None and (options.rou is None or options.net is None or \
                                options.start is None or options.end is None):
        optparser.print_help()
        exit(0)
    if options.logs is None:
        optparser.print_help()
        exit(0)
    return options


def parse_cfg(filename):
    assert os.path.isfile(filename)
    dirname = os.path.dirname(filename)
    tree = ET.ElementTree(file=filename)
    config = {}
    for child in tree.getroot():
        if child.tag == 'input':
            for node in child:
                if node.tag == 'net-file':
                    config.setdefault("net", os.path.join(dirname, node.attrib['value']))
                elif node.tag == 'route-files':
                    config.setdefault("rou", os.path.join(dirname, node.attrib['value']))
        elif child.tag == 'time':
            for node in child:
                if node.tag == 'begin':
                    config.setdefault("begin", int(node.attrib['value']))
                elif node.tag == 'end':
                    config.setdefault("end", int(node.attrib['value']))
    return config


def test_parse_cfg():
    filename = "../../data/10221507.sumo.cfg"
    config = parse_cfg(filename)
    print config


def main(options=None):
    assert not os.path.isfile(options.logs)
    logging.basicConfig(level=logging.INFO, format="%(message)s", filename=options.logs, filemode="a")
    configuration = {}
    if options.cfg is not None:
        configuration = parse_cfg(options.cfg)
    if options.rou is not None:
        configuration['rou'] = options.rou
    if options.net is not None:
        configuration['net'] = options.net
    if options.start is not None:
        configuration['begin'] = options.start
    if options.end is not None:
        configuration['end'] = options.end
    if options.server is not None:
        configuration['host'] = options.server
    if options.port is not None:
        configuration['port'] = options.port
    configuration['class_name'] = "SimpleVelocityController"
    if options.class_name is not None:
        configuration['class_name'] = options.class_name

    # check parameter
    if 'rou' not in configuration:
        print('check route xml file')
        exit(0)
    if 'net' not in configuration:
        print("check net xml file")
        exit(0)
    if 'begin' not in configuration:
        print("check begin time ")
        exit(0)
    if 'end' not in configuration:
        print("check end time")
        exit(0)
    if 'port' not in configuration:
        print("check remote port")
        exit(0)
    if "host" not in configuration:
        print("check server ip address")
        exit(0)

    connector = Connector(network_file=configuration['net'], port=configuration['port'], host=configuration['host'],\
                          start=configuration['begin'], end=configuration['end'])
    controller = None
    if configuration['class_name'] == 'SimpleVelocityController':
        controller = SimpleVelocityController(rou_file=configuration['rou'])
    elif configuration['class_name'] == 'AverageVelocityController':
        controller = AverageVelocityController(rou_file=configuration['rou'])

    connector.register_controller(controller)
    connector.simulate()

if __name__ == "__main__":
    main(get_options())
    # test_parse_cfg()
